/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.graphics

import androidx.compose.runtime.Immutable

/**
 * Algorithms to use when painting on the canvas.
 *
 * When drawing a shape or image onto a canvas, different algorithms can be used to blend the
 * pixels. The different values of [BlendMode] specify different such algorithms.
 *
 * Each algorithm has two inputs, the _source_, which is the image being drawn, and the
 * _destination_, which is the image into which the source image is being composited. The
 * destination is often thought of as the _background_. The source and destination both have four
 * color channels, the red, green, blue, and alpha channels. These are typically represented as
 * numbers in the range 0.0 to 1.0. The output of the algorithm also has these same four channels,
 * with values computed from the source and destination.
 *
 * ## Application to the [Canvas] API
 *
 * When using [Canvas.saveLayer] and [Canvas.restore], the blend mode of the [Paint] given to the
 * [Canvas.saveLayer] will be applied when [Canvas.restore] is called. Each call to
 * [Canvas.saveLayer] introduces a new layer onto which shapes and images are painted; when
 * [Canvas.restore] is called, that layer is then composited onto the parent layer, with the source
 * being the most-recently-drawn shapes and images, and the destination being the parent layer. (For
 * the first [Canvas.saveLayer] call, the parent layer is the canvas itself.)
 *
 * See also:
 * * [Paint.blendMode], which uses [BlendMode] to define the compositing strategy.
 */
@Immutable
@kotlin.jvm.JvmInline
value class BlendMode internal constructor(@Suppress("unused") private val value: Int) {

    companion object {

        /** Drop both the source and destination images, leaving nothing. */
        val Clear = BlendMode(0)

        /**
         * Drop the destination image, only paint the source image.
         *
         * Conceptually, the destination is first cleared, then the source image is painted.
         */
        val Src = BlendMode(1)

        /**
         * Drop the source image, only paint the destination image.
         *
         * Conceptually, the source image is discarded, leaving the destination untouched.
         */
        val Dst = BlendMode(2)

        /**
         * Composite the source image over the destination image.
         *
         * This is the default value. It represents the most intuitive case, where shapes are
         * painted on top of what is below, with transparent areas showing the destination layer.
         */
        val SrcOver = BlendMode(3)

        /**
         * Composite the source image under the destination image.
         *
         * This is the opposite of [SrcOver].
         *
         * This is useful when the source image should have been painted before the destination
         * image, but could not be.
         */
        val DstOver = BlendMode(4)

        /**
         * Show the source image, but only where the two images overlap. The destination image is
         * not rendered, it is treated merely as a mask. The color channels of the destination are
         * ignored, only the opacity has an effect.
         *
         * To show the destination image instead, consider [DstIn].
         *
         * To reverse the semantic of the mask (only showing the source where the destination is
         * absent, rather than where it is present), consider [SrcOut].
         */
        val SrcIn = BlendMode(5)

        /**
         * Show the destination image, but only where the two images overlap. The source image is
         * not rendered, it is treated merely as a mask. The color channels of the source are
         * ignored, only the opacity has an effect.
         *
         * To show the source image instead, consider [SrcIn].
         *
         * To reverse the semantic of the mask (only showing the source where the destination is
         * present, rather than where it is absent), consider [DstOut].
         */
        val DstIn = BlendMode(6)

        /**
         * Show the source image, but only where the two images do not overlap. The destination
         * image is not rendered, it is treated merely as a mask. The color channels of the
         * destination are ignored, only the opacity has an effect.
         *
         * To show the destination image instead, consider [DstOut].
         *
         * To reverse the semantic of the mask (only showing the source where the destination is
         * present, rather than where it is absent), consider [SrcIn].
         *
         * This corresponds to the "Source out Destination" Porter-Duff operator.
         */
        val SrcOut = BlendMode(7)

        /**
         * Show the destination image, but only where the two images do not overlap. The source
         * image is not rendered, it is treated merely as a mask. The color channels of the source
         * are ignored, only the opacity has an effect.
         *
         * To show the source image instead, consider [SrcOut].
         *
         * To reverse the semantic of the mask (only showing the destination where the source is
         * present, rather than where it is absent), consider [DstIn].
         *
         * This corresponds to the "Destination out Source" Porter-Duff operator.
         */
        val DstOut = BlendMode(8)

        /**
         * Composite the source image over the destination image, but only where it overlaps the
         * destination.
         *
         * This is essentially the [SrcOver] operator, but with the output's opacity channel being
         * set to that of the destination image instead of being a combination of both image's
         * opacity channels.
         *
         * For a variant with the destination on top instead of the source, see [DstAtop].
         */
        val SrcAtop = BlendMode(9)

        /**
         * Composite the destination image over the source image, but only where it overlaps the
         * source.
         *
         * This is essentially the [DstOver] operator, but with the output's opacity channel being
         * set to that of the source image instead of being a combination of both image's opacity
         * channels.
         *
         * For a variant with the source on top instead of the destination, see [SrcAtop].
         */
        val DstAtop = BlendMode(10)

        /**
         * Apply a bitwise `xor` operator to the source and destination images. This leaves
         * transparency where they would overlap.
         */
        val Xor = BlendMode(11)

        /**
         * Sum the components of the source and destination images.
         *
         * Transparency in a pixel of one of the images reduces the contribution of that image to
         * the corresponding output pixel, as if the color of that pixel in that image was darker.
         */
        val Plus = BlendMode(12)

        /**
         * Multiply the color components of the source and destination images.
         *
         * This can only result in the same or darker colors (multiplying by white, 1.0, results in
         * no change; multiplying by black, 0.0, results in black).
         *
         * When compositing two opaque images, this has similar effect to overlapping two
         * transparencies on a projector.
         *
         * For a variant that also multiplies the alpha channel, consider [Multiply].
         *
         * See also:
         * * [Screen], which does a similar computation but inverted.
         * * [Overlay], which combines [Modulate] and [Screen] to favor the destination image.
         * * [Hardlight], which combines [Modulate] and [Screen] to favor the source image.
         */
        val Modulate = BlendMode(13)

        /**
         * Multiply the inverse of the components of the source and destination images, and inverse
         * the result.
         *
         * Inverting the components means that a fully saturated channel (opaque white) is treated
         * as the value 0.0, and values normally treated as 0.0 (black, transparent) are treated as
         * 1.0.
         *
         * This is essentially the same as [Modulate] blend mode, but with the values of the colors
         * inverted before the multiplication and the result being inverted back before rendering.
         *
         * This can only result in the same or lighter colors (multiplying by black, 1.0, results in
         * no change; multiplying by white, 0.0, results in white). Similarly, in the alpha channel,
         * it can only result in more opaque colors.
         *
         * This has similar effect to two projectors displaying their images on the same screen
         * simultaneously.
         *
         * See also:
         * * [Modulate], which does a similar computation but without inverting the values.
         * * [Overlay], which combines [Modulate] and [Screen] to favor the destination image.
         * * [Hardlight], which combines [Modulate] and [Screen] to favor the source image.
         */
        val Screen = BlendMode(14) // The last coeff mode.

        /**
         * Multiply the components of the source and destination images after adjusting them to
         * favor the destination.
         *
         * Specifically, if the destination value is smaller, this multiplies it with the source
         * value, whereas if the source value is smaller, it multiplies the inverse of the source
         * value with the inverse of the destination value, then inverts the result.
         *
         * Inverting the components means that a fully saturated channel (opaque white) is treated
         * as the value 0.0, and values normally treated as 0.0 (black, transparent) are treated as
         * 1.0.
         *
         * See also:
         * * [Modulate], which always multiplies the values.
         * * [Screen], which always multiplies the inverses of the values.
         * * [Hardlight], which is similar to [Overlay] but favors the source image instead of the
         *   destination image.
         */
        val Overlay = BlendMode(15)

        /**
         * Composite the source and destination image by choosing the lowest value from each color
         * channel.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver].
         */
        val Darken = BlendMode(16)

        /**
         * Composite the source and destination image by choosing the highest value from each color
         * channel.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver].
         */
        val Lighten = BlendMode(17)

        /**
         * Divide the destination by the inverse of the source.
         *
         * Inverting the components means that a fully saturated channel (opaque white) is treated
         * as the value 0.0, and values normally treated as 0.0 (black, transparent) are treated as
         * 1.0.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         */
        val ColorDodge = BlendMode(18)

        /**
         * Divide the inverse of the destination by the source, and inverse the result.
         *
         * Inverting the components means that a fully saturated channel (opaque white) is treated
         * as the value 0.0, and values normally treated as 0.0 (black, transparent) are treated as
         * 1.0.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         */
        val ColorBurn = BlendMode(19)

        /**
         * Multiply the components of the source and destination images after adjusting them to
         * favor the source.
         *
         * Specifically, if the source value is smaller, this multiplies it with the destination
         * value, whereas is the destination value is smaller, it multiplies the inverse of the
         * destination value with the inverse of the source value, then inverts the result.
         *
         * Inverting the components means that a fully saturated channel (opaque white) is treated
         * as the value 0.0, and values normally treated as 0.0 (black, transparent) are treated as
         * 1.0.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * See also:
         * * [Modulate], which always multiplies the values.
         * * [Screen], which always multiplies the inverses of the values.
         * * [Overlay], which is similar to [Hardlight] but favors the destination image instead of
         *   the source image.
         */
        val Hardlight = BlendMode(20)

        /**
         * Use [ColorDodge] for source values below 0.5 and [ColorBurn] for source values above 0.5.
         *
         * This results in a similar but softer effect than [Overlay].
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * See also:
         * * [BlendMode.Color], which is a more subtle tinting effect.
         */
        val Softlight = BlendMode(21)

        /**
         * Subtract the smaller value from the bigger value for each channel.
         *
         * Compositing black has no effect; compositing white inverts the colors of the other image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver].
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * The effect is similar to [Exclusion] but harsher.
         */
        val Difference = BlendMode(22)

        /**
         * Subtract double the product of the two images from the sum of the two images.
         *
         * Compositing black has no effect; compositing white inverts the colors of the other image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver].
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * The effect is similar to [Difference] but softer.
         */
        val Exclusion = BlendMode(23)

        /**
         * Multiply the components of the source and destination images, including the alpha
         * channel.
         *
         * This can only result in the same or darker colors (multiplying by white, 1.0, results in
         * no change; multiplying by black, 0.0, results in black).
         *
         * Since the alpha channel is also multiplied, a fully-transparent pixel (opacity 0.0) in
         * one image results in a fully transparent pixel in the output. This is similar to [DstIn],
         * but with the colors combined.
         *
         * For a variant that multiplies the colors but does not multiply the alpha channel,
         * consider [Modulate].
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         */
        val Multiply = BlendMode(24) // The last separable mode.

        /**
         * Take the hue of the source image, and the saturation and luminosity of the destination
         * image.
         *
         * The effect is to tint the destination image with the source image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver]. Regions
         * that are entirely transparent in the source image take their hue from the destination.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         */
        val Hue = BlendMode(25)

        /**
         * Take the saturation of the source image, and the hue and luminosity of the destination
         * image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver]. Regions
         * that are entirely transparent in the source image take their saturation from the
         * destination.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * See also:
         * * [BlendMode.Color], which also applies the hue of the source image.
         * * [Luminosity], which applies the luminosity of the source image to the destination.
         */
        val Saturation = BlendMode(26)

        /**
         * Take the hue and saturation of the source image, and the luminosity of the destination
         * image.
         *
         * The effect is to tint the destination image with the source image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver]. Regions
         * that are entirely transparent in the source image take their hue and saturation from the
         * destination.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * See also:
         * * [Hue], which is a similar but weaker effect.
         * * [Softlight], which is a similar tinting effect but also tints white.
         * * [Saturation], which only applies the saturation of the source image.
         */
        val Color = BlendMode(27)

        /**
         * Take the luminosity of the source image, and the hue and saturation of the destination
         * image.
         *
         * The opacity of the output image is computed in the same way as for [SrcOver]. Regions
         * that are entirely transparent in the source image take their luminosity from the
         * destination.
         *
         * **NOTE** This [BlendMode] can only be used on Android API level 29 and above
         *
         * See also:
         * * [Saturation], which applies the saturation of the source image to the destination.
         */
        val Luminosity = BlendMode(28)
    }

    override fun toString() =
        when (this) {
            Clear -> "Clear"
            Src -> "Src"
            Dst -> "Dst"
            SrcOver -> "SrcOver"
            DstOver -> "DstOver"
            SrcIn -> "SrcIn"
            DstIn -> "DstIn"
            SrcOut -> "SrcOut"
            DstOut -> "DstOut"
            SrcAtop -> "SrcAtop"
            DstAtop -> "DstAtop"
            Xor -> "Xor"
            Plus -> "Plus"
            Modulate -> "Modulate"
            Screen -> "Screen"
            Overlay -> "Overlay"
            Darken -> "Darken"
            Lighten -> "Lighten"
            ColorDodge -> "ColorDodge"
            ColorBurn -> "ColorBurn"
            Hardlight -> "HardLight"
            Softlight -> "Softlight"
            Difference -> "Difference"
            Exclusion -> "Exclusion"
            Multiply -> "Multiply"
            Hue -> "Hue"
            Saturation -> "Saturation"
            Color -> "Color"
            Luminosity -> "Luminosity"
            else -> "Unknown" // Should not get here since we have an internal constructor
        }
}

/**
 * Capability query to determine if the particular platform supports the [BlendMode]. Not all
 * platforms support all blend mode algorithms, however, [BlendMode.SrcOver] is guaranteed to be
 * supported as it is the default drawing algorithm. If a [BlendMode] that is not supported is used,
 * the default of SrcOver is consumed instead.
 */
expect fun BlendMode.isSupported(): Boolean
